#include "mat4.h"
#include "dmat4.h"

namespace libvec
{
  mat4::mat4()
  {
    vec4 v(0);

    this->v1 = v;
    this->v2 = v;
    this->v3 = v;
    this->v4 = v;
  }

  mat4::mat4(dmat4 m)
  {
    this->v1 = m.v1;
    this->v2 = m.v2;
    this->v3 = m.v3;
    this->v4 = m.v4;
  }

  mat4::mat4(float value)
  {
    this->v1.x = value;
    this->v2.y = value;
    this->v3.z = value;
    this->v4.w = value;
  }

  mat4::mat4(float n11, float n12, float n13, float n14,
      float n21, float n22, float n23, float n24,
      float n31, float n32, float n33, float n34,
      float n41, float n42, float n43, float n44)
  {
    this->v1.x = n11;
    this->v2.x = n12;
    this->v3.x = n13;
    this->v4.x = n14;

    this->v1.y = n21;
    this->v2.y = n22;
    this->v3.y = n23;
    this->v4.y = n24;

    this->v1.z = n31;
    this->v2.z = n32;
    this->v3.z = n33;
    this->v4.z = n34;

    this->v1.w = n41;
    this->v2.w = n42;
    this->v3.w = n43;
    this->v4.w = n44;
  }

  mat4::mat4(vec4 v1, vec4 v2, vec4 v3, vec4 v4)
  {
    this->v1 = v1;
    this->v2 = v2;
    this->v3 = v3;
    this->v4 = v4;
  }

  vec4 &mat4::operator[](int index)
  {
    static vec4 empty;

    switch (index)
    {
      case 0:
        return this->v1;

      case 1:
        return this->v2;

      case 2:
        return this->v3;

      case 3:
        return this->v4;
    }

    return empty;
  }

  mat4 mat4::operator=(mat4 m)
  {
    this->v1 = m.v1;
    this->v2 = m.v2;
    this->v3 = m.v3;
    this->v4 = m.v4;

    return *this;
  }

  bool mat4::operator==(mat4 m)
  {
    return (this->v1 == m.v1) && (this->v2 == m.v2) && (this->v3 == m.v3) &&
      (this->v4 == m.v4);
  }

  bool mat4::operator!=(mat4 m)
  {
    return (this->v1 != m.v1) || (this->v2 != m.v2) || (this->v4 != m.v4) ||
      (this->v4 != m.v4);
  }

  mat4 &mat4::operator+()
  {
    return *this;
  }

  mat4 &mat4::operator-()
  {
    static mat4 out;

    out.v1 = -this->v1;
    out.v2 = -this->v2;
    out.v3 = -this->v3;
    out.v4 = -this->v4;

    return out;
  }

  mat4 mat4::operator+(mat4 m)
  {
    static mat4 out;

    out.v1 = this->v1 + m.v1;
    out.v2 = this->v2 + m.v2;
    out.v3 = this->v3 + m.v3;
    out.v4 = this->v4 + m.v4;

    return out;
  }

  mat4 mat4::operator+=(mat4 m)
  {
    this->v1 += m.v1;
    this->v2 += m.v2;
    this->v3 += m.v3;
    this->v4 += m.v4;

    return *this;
  }

  mat4 mat4::operator-(mat4 m)
  {
    static mat4 out;

    out.v1 = this->v1 - m.v1;
    out.v2 = this->v2 - m.v2;
    out.v3 = this->v3 - m.v3;
    out.v4 = this->v4 - m.v4;

    return out;
  }

  mat4 mat4::operator-=(mat4 m)
  {
    this->v1 -= m.v1;
    this->v2 -= m.v2;
    this->v3 -= m.v3;
    this->v4 -= m.v4;

    return *this;
  }

  vec4 mat4::operator*(vec4 v)
  {
    static vec4 out;

    out.x = this->v1.x*v.x + this->v2.x*v.y + this->v3.x*v.z + this->v4.x*v.w;
    out.y = this->v1.y*v.x + this->v2.y*v.y + this->v3.y*v.z + this->v4.y*v.w;
    out.z = this->v1.z*v.x + this->v2.z*v.y + this->v3.z*v.z + this->v4.z*v.w;
    out.w = this->v1.w*v.x + this->v2.w*v.y + this->v3.w*v.z + this->v4.w*v.w;

    return out;
  }

  mat4 mat4::operator*(mat4 m)
  {
    static mat4 out;

    out.v1.x = this->v1.x*m.v1.x + this->v2.x*m.v1.y + this->v3.x*m.v1.z + this->v4.x*m.v1.w;
    out.v2.x = this->v1.x*m.v2.x + this->v2.x*m.v2.y + this->v3.x*m.v2.z + this->v4.x*m.v2.w;
    out.v3.x = this->v1.x*m.v3.x + this->v2.x*m.v3.y + this->v3.x*m.v3.z + this->v4.x*m.v3.w;
    out.v4.x = this->v1.x*m.v4.x + this->v2.x*m.v4.y + this->v3.x*m.v4.z + this->v4.x*m.v4.w;

    out.v1.y = this->v1.y*m.v1.x + this->v2.y*m.v1.y + this->v3.y*m.v1.z + this->v4.y*m.v1.w;
    out.v2.y = this->v1.y*m.v2.x + this->v2.y*m.v2.y + this->v3.y*m.v2.z + this->v4.y*m.v2.w;
    out.v3.y = this->v1.y*m.v3.x + this->v2.y*m.v3.y + this->v3.y*m.v3.z + this->v4.y*m.v3.w;
    out.v4.y = this->v1.y*m.v4.x + this->v2.y*m.v4.y + this->v3.y*m.v4.z + this->v4.y*m.v4.w;

    out.v1.z = this->v1.z*m.v1.x + this->v2.z*m.v1.y + this->v3.z*m.v1.z + this->v4.z*m.v1.w;
    out.v2.z = this->v1.z*m.v2.x + this->v2.z*m.v2.y + this->v3.z*m.v2.z + this->v4.z*m.v2.w;
    out.v3.z = this->v1.z*m.v3.x + this->v2.z*m.v3.y + this->v3.z*m.v3.z + this->v4.z*m.v3.w;
    out.v4.z = this->v1.z*m.v4.x + this->v2.z*m.v4.y + this->v3.z*m.v4.z + this->v4.z*m.v4.w;

    out.v1.w = this->v1.w*m.v1.x + this->v2.w*m.v1.y + this->v3.w*m.v1.z + this->v4.w*m.v1.w;
    out.v2.w = this->v1.w*m.v2.x + this->v2.w*m.v2.y + this->v3.w*m.v2.z + this->v4.w*m.v2.w;
    out.v3.w = this->v1.w*m.v3.x + this->v2.w*m.v3.y + this->v3.w*m.v3.z + this->v4.w*m.v3.w;
    out.v4.w = this->v1.w*m.v4.x + this->v2.w*m.v4.y + this->v3.w*m.v4.z + this->v4.w*m.v4.w;

    return out;
  }

  mat4 mat4::operator*=(mat4 m)
  {
    static mat4 out;

    out.v1.x = this->v1.x*m.v1.x + this->v2.x*m.v1.y + this->v3.x*m.v1.z + this->v4.x*m.v1.w;
    out.v2.x = this->v1.x*m.v2.x + this->v2.x*m.v2.y + this->v3.x*m.v2.z + this->v4.x*m.v2.w;
    out.v3.x = this->v1.x*m.v3.x + this->v2.x*m.v3.y + this->v3.x*m.v3.z + this->v4.x*m.v3.w;
    out.v4.x = this->v1.x*m.v4.x + this->v2.x*m.v4.y + this->v3.x*m.v4.z + this->v4.x*m.v4.w;

    out.v1.y = this->v1.y*m.v1.x + this->v2.y*m.v1.y + this->v3.y*m.v1.z + this->v4.y*m.v1.w;
    out.v2.y = this->v1.y*m.v2.x + this->v2.y*m.v2.y + this->v3.y*m.v2.z + this->v4.y*m.v2.w;
    out.v3.y = this->v1.y*m.v3.x + this->v2.y*m.v3.y + this->v3.y*m.v3.z + this->v4.y*m.v3.w;
    out.v4.y = this->v1.y*m.v4.x + this->v2.y*m.v4.y + this->v3.y*m.v4.z + this->v4.y*m.v4.w;

    out.v1.z = this->v1.z*m.v1.x + this->v2.z*m.v1.y + this->v3.z*m.v1.z + this->v4.z*m.v1.w;
    out.v2.z = this->v1.z*m.v2.x + this->v2.z*m.v2.y + this->v3.z*m.v2.z + this->v4.z*m.v2.w;
    out.v3.z = this->v1.z*m.v3.x + this->v2.z*m.v3.y + this->v3.z*m.v3.z + this->v4.z*m.v3.w;
    out.v4.z = this->v1.z*m.v4.x + this->v2.z*m.v4.y + this->v3.z*m.v4.z + this->v4.z*m.v4.w;

    out.v1.w = this->v1.w*m.v1.x + this->v2.w*m.v1.y + this->v3.w*m.v1.z + this->v4.w*m.v1.w;
    out.v2.w = this->v1.w*m.v2.x + this->v2.w*m.v2.y + this->v3.w*m.v2.z + this->v4.w*m.v2.w;
    out.v3.w = this->v1.w*m.v3.x + this->v2.w*m.v3.y + this->v3.w*m.v3.z + this->v4.w*m.v3.w;
    out.v4.w = this->v1.w*m.v4.x + this->v2.w*m.v4.y + this->v3.w*m.v4.z + this->v4.w*m.v4.w;

    this->v1 = out.v1;
    this->v2 = out.v2;
    this->v3 = out.v3;
    this->v4 = out.v4;

    return out;
  }

  mat4 mat4::operator*(float scale)
  {
    static mat4 out;

    out.v1 = this->v1 * scale;
    out.v2 = this->v2 * scale;
    out.v3 = this->v3 * scale;
    out.v4 = this->v4 * scale;

    return out;
  }

  mat4 mat4::operator*=(float scale)
  {
    this->v1 *= scale;
    this->v2 *= scale;
    this->v3 *= scale;
    this->v4 *= scale;

    return *this;
  }

  mat4 mat4::operator/(float scale)
  {
    static mat4 out;

    out.v1 = this->v1 / scale;
    out.v2 = this->v2 / scale;
    out.v3 = this->v3 / scale;
    out.v4 = this->v4 / scale;

    return out;
  }

  mat4 mat4::operator/=(float scale)
  {
    this->v1 /= scale;
    this->v2 /= scale;
    this->v3 /= scale;
    this->v4 /= scale;

    return *this;
  }
}

